<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>音频可视化</title>
    <style>
        body{
			font-family: Arial, sans-serif;
			background-color: #282C34;
			position: relative;
			margin: 0;
			padding: 0;
            /*background-image: url("1.jpg");*/
            background-size: cover;
            background-position: 0 -380px;
            backdrop-filter: blur(10px);
            background-repeat: no-repeat;
		}
        #fileBox{
            width: 100px;
            height: 40px;
            line-height: 40px;
            font-size: 15px;
            color: gold;
            background: rgba(0,0,0,0.2);
            border-radius: 10px;
            border: none;
            margin-right: 10px;
            text-align: center;
            cursor: pointer;
        }
        #file{
            opacity: 0;
            width: 100px;
            height: 40px;
            position: absolute;
            left: 0px;
        }
		#wrap-box{
			position: absolute;
			left: 23%;
            margin-top: 150px;
		}
		#img {
			width: 300px;
			height: 300px;
			/*background-image: url('1.jpg');*/
			background-size: cover;
			background-position: center;
			border-radius: 50%;
			animation: rotate 15s infinite linear;
			position: absolute;
			top: 20%;
			left: 20%;
			z-index: -1;
		}

		@keyframes rotate {
			from {
				transform: rotate(0deg);
			}
			to {
				transform: rotate(360deg);
			}
		}

        /* 歌词特效 */
        .lyrics-box{
            position: absolute;
            right: 15%;
            height: 800px;
            display: flex;
            /* 投影效果 */
            -webkit-box-reflect:below 1px linear-gradient(transparent, rgba(0,0,0,0.2));
        }

        #lyrics {
            writing-mode: vertical-rl;
            text-orientation: upright;
            white-space: nowrap;
            /* overflow: hidden; */
            font-size: 32px;
            font-style: italic;
            color: #fff;
            /* 字间距 */
            letter-spacing: 10px;
            text-align: center;
            vertical-align: middle;
            outline: none;

            /* 自定义属性 --c,可通过 var 函数对其调用 */
            --c:lightseagreen;
            /* 调用自定义属性--c，设置文字阴影（发光效果） */
            text-shadow: 0 0 10px var(--c),
            0 0 20px var(--c),
            0 0 40px var(--c),
            0 0 80px var(--c),
            0 0 160px var(--c);
            /* 执行动画：动画名 时长 线性的 无限次播放 */
            animation: animate 5s linear infinite;
        }

        /* 定义动画 */
        @keyframes animate{
            to{
                /* 色相旋转过滤镜（设置度数可改变颜色） */
                filter: hue-rotate(360deg);
            }
        }
</style>
    </style>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
    <div class="main" style="height: 0;">
        <div class="fileBox" id="fileBox">导入
            <input type="file" id="file" class="file" multiple="multiple" accept="*">
        </div>
        <div id="wrap-box">
            <canvas id="wrap" width="500" height="500"></canvas>
            <div id="img"></div>
        </div>
        <div class="lyrics-box">
            <p id="lyrics"></p>
        </div>
    </div>


    <script>
        let file = document.getElementById('file');
        let fileAudio; // 音频文件
        let fileLyrics; // 歌词文件
        let audio = new Audio();

        // 音频可视化
        //wrap.width = window.innerWidth;
        //wrap.height = window.innerHeight-120;

        const canvasCtx = wrap.getContext("2d");
        const AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
        let audioContext = new AudioContext();//实例化
        let sources; // 音频源

        /**
         * 加载音乐文件列表
         */
        file.onchange = function (e){
            if (e.target.files.length == 1){ // 只上传了音频文件
                fileAudio = e.target.files[0];
                loadMusicAndPlay();
            }else if (e.target.files.length == 2){ // 上传了音频文件和歌词文件
                var file0 = e.target.files[0];
                var file1 = e.target.files[1];
                var file0Name = file0.name;

                const extension = file0Name.split('.').pop().toLowerCase();
                if (extension === 'lrc') {
                    fileLyrics = file0;
                    fileAudio = file1;
                }else{
                    fileLyrics = file1;
                    fileAudio = file0;
                }

                // 读取歌词文件内容
                const lyricsReader = new FileReader();
                lyricsReader.readAsText(fileLyrics);
                // 解析歌词文件
                const lyrics = [];
                lyricsReader.onload = function() {
                    const lines = this.result.split('\n');
                    const regex = /\[(\d{2}):(\d{2})\.(\d{2})\](.*)/;
                    for (let i = 0; i < lines.length; i++) {
                        const matches = regex.exec(lines[i]);
                        if (matches) {
                            const time = parseInt(matches[1]) * 60 + parseInt(matches[2]) + parseInt(matches[3]) / 100;
                            const text = matches[4];
                            lyrics.push({ time, text });
                        }
                    }
                };

                loadMusicAndPlay(); // 加载音乐并播放
                // 设置定时器，刷新歌词
                setInterval(function() {
                    const currentTime = audio.currentTime;
                    for (let i = 0; i < lyrics.length; i++) {
                        if (lyrics[i].time > currentTime) {
                            $('#lyrics').text(lyrics[i - 1].text);
                            break;
                        }
                    }
                }, 100);
            }
        }

        /**
         * 加载音乐、播放
         */
        function loadMusicAndPlay(){
            audioContext.resume();
            var fileReader = new FileReader();
            fileReader.readAsDataURL(fileAudio); // 文件读取为url
            fileReader.onload = function(e) {
                audio.preload = "auto";
                audio.src = e.target.result;
                // 当音频源为undefined时才创建
                if (Object.is(sources,undefined)){
                    //创建一个MediaElementAudioSourceNode接口来关联audio元素的音频。
                    sources = audioContext.createMediaElementSource(audio);
                }
                var analyser = audioContext.createAnalyser();
                // 连接到音频分析器，分析频谱
                sources.connect(analyser);
                analyser.connect(audioContext.destination);
                绘制频谱(analyser,file.name); // 绘制音频频谱
                audio.play();
            }
        }

        function 绘制频谱(analyser,name){
            var oW = wrap.width;
            var oH = wrap.height;

            var color1 = canvasCtx.createLinearGradient(oW/2, 0, oW, oH / 2);
            color1.addColorStop(0, '#FF4500');
            color1.addColorStop(.25, '#fff');
            color1.addColorStop(.5, '#ff0000');
            color1.addColorStop(.75, '#ff0000');
            color1.addColorStop(1, '#FF4500');

            var color2=canvasCtx.createLinearGradient(oW/2, 0, oW, oH / 2);
            color2.addColorStop(0, '#ffff00');
            color2.addColorStop(.25, '#33a6b8');
            color2.addColorStop(.5, '#fff');
            color2.addColorStop(.75, '#1e88a8');
            color2.addColorStop(1, '#2ea9df');

            var output = new Uint8Array(360);
            var du1 = 2,// 外面的圆角度
                du2 = 1,// 里面的圆角度
                R1 = 160,// 外面的圆半径
                R2 = 150,// 里面的圆半径
                W1 = 4,// 外圆线条的宽
                W2 = 2;// 内圆线条的宽
            (function drawSpectrum() {
                analyser.getByteFrequencyData(output);
                canvasCtx.clearRect(0, 0, wrap.width, wrap.height);
                for (var i = 0; i < 360; i++) {
                    var value = output[i] / 8;
                    var distance = R1 - value; // 当前点到圆心的距离
                    if (distance > 0) { // 判断是否在圆的外面
                        canvasCtx.beginPath();
                        canvasCtx.lineWidth = W1;
                        Rv1 = distance;
                        Rv2 = R1 + value + 10;
                        var x1 = (Math.sin((i * du1) / 180 * Math.PI) * Rv1 + oW/2);
                        var y1 = -Math.cos((i * du1) / 180 * Math.PI) * Rv1 + oH/2;
                        var x2 = (Math.sin((i * du1) / 180 * Math.PI) * Rv2 + oW/2);
                        var y2 = -Math.cos((i * du1) / 180 * Math.PI) * Rv2 + oH/2;
                        if (distance <= R1) { // 判断是否在圆的内部
                            var angle = Math.atan2(y2 - oH/2, x2 - oW/2); // 计算线条与圆心的连线的角度
                            x1 = Math.cos(angle) * R1 + oW/2; // 将起点设置为圆上与线条相交的点
                            y1 = Math.sin(angle) * R1 + oH/2;
                        }
                        canvasCtx.moveTo(x1, y1);
                        canvasCtx.lineTo(x2, y2);
                        canvasCtx.strokeStyle = color1; // 线条的颜色
                        canvasCtx.stroke();
                    }
                }
                for (var i = 0; i < 360; i++) {
                    var value = output[i] / 8;
                    var distance = R2 - value; // 当前点到圆心的距离
                    if (distance > 0) { // 判断是否在圆的外面
                        canvasCtx.beginPath();
                        canvasCtx.lineWidth = W2;
                        Rv1 = distance;
                        Rv2 = R2 - value; // 修改这里，将半径减去value
                        var x1 = (Math.sin((i * du2) / 180 * Math.PI) * Rv1 + oW / 2);
                        var y1 = -Math.cos((i * du2) / 180 * Math.PI) * Rv1 + oH / 2;
                        var x2 = (Math.sin((i * du2) / 180 * Math.PI) * Rv2 + oW / 2);
                        var y2 = -Math.cos((i * du2) / 180 * Math.PI) * Rv2 + oH / 2;
                        if (distance <= R2) { // 判断是否在圆的内部
                            var angle = Math.atan2(y2 - oH / 2, x2 - oW / 2); // 计算线条与圆心的连线的角度
                            x1 = Math.cos(angle) * R2 + oW / 2; // 将起点设置为圆上与线条相交的点
                            y1 = Math.sin(angle) * R2 + oH / 2;
                        }
                        canvasCtx.moveTo(x1, y1);
                        canvasCtx.lineTo(x2, y2);
                        canvasCtx.strokeStyle = color2; // 线条的颜色
                        canvasCtx.stroke();
                    }
                }
                requestAnimationFrame(drawSpectrum);
            })();
        }
    </script>
</body>
</html>
